package com.desaysv.svhttp.handler.impl;

import android.annotation.SuppressLint;
import android.os.Handler;
import android.os.Looper;
import android.text.TextUtils;
import android.util.Log;

import com.desaysv.svhttp.BuildConfig;
import com.desaysv.svhttp.SVHttpProxy;
import com.desaysv.svhttp.callback.IBaseCallBack;
import com.desaysv.svhttp.callback.IDownLoadCallBack;
import com.desaysv.svhttp.callback.IRequestCallBack;
import com.desaysv.svhttp.coder.IResponseDecoder;
import com.desaysv.svhttp.handler.IHandler;
import com.desaysv.svhttp.handler.type.HttpType;
import com.desaysv.svhttp.interceptor.IInterceptor;
import com.desaysv.svhttp.param.HttpParams;
import com.desaysv.svhttp.utils.SVUtils;
import com.google.gson.Gson;

import java.io.ByteArrayInputStream;
import java.io.Closeable;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.X509TrustManager;

/**
 * 网络代理类
 *
 * @author linzi
 * @version 1.0
 * @date 2022-7-19
 * @since 2022-7-19
 */
public final class HttpHandler implements IHandler<HttpHandler> {
    /**
     * 基础路径
     */
    private String mBaseUrl = "";
    /**
     * 响应解码
     */
    private IResponseDecoder mResDecoder = null;
    /**
     * 线程池，允许并发执行50个请求
     */
    private static final int THREAD_MAX = 50;
    /**
     * 线程池管理
     */
    private final ExecutorService mExecutor = Executors.newFixedThreadPool(THREAD_MAX);

    /**
     * 连接超时时间
     */
    private final int mTimeout = 15 * 1000;
    /**
     * 读取超时时间
     */
    private final int mReadTimeout = 30 * 1000;
    /**
     * 是否使用缓存
     */
    private final boolean mUseCache = false;
    /**
     * mUiHandler
     */
    private final Handler mUiHandler = new Handler(Looper.getMainLooper());
    /**
     * @date 2022-8-26 9:45
     * @context 拦截器
     */
    private final ConcurrentHashMap<String, IInterceptor> mInterceptors = new ConcurrentHashMap<>();

    /**
     * 日志TAG
     */
    private static final String TAG = "[HttpHandler]";

    /**
     * 构造函数
     */
    private HttpHandler() {
        if (BuildConfig.DEBUG) {
            Log.i(TAG, "【HTTP】HttpHandler init");
        }
        if (SVHttpProxy.getFactory().checkInit(this.getClass())) {
            throw new RuntimeException("has init !!! please use SVHttpProxy.getFactory().getProxy(HttpHandler.class) to get instance for this class");
        }
        //忽略证书，后续可考虑将https证书配置交给接入方配置
        try {
            SSLContext sslcontext = SSLContext.getInstance("SSL");
            X509TrustManager[] tm = {new CusX509TrustManager()};
            sslcontext.init(null, tm, new SecureRandom());
            HostnameVerifier ignoreHostnameVerifier = (s, sslsession) -> true;
            HttpsURLConnection.setDefaultHostnameVerifier(ignoreHostnameVerifier);
            HttpsURLConnection.setDefaultSSLSocketFactory(sslcontext.getSocketFactory());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * @author linzi
     * @date 2022-8-17 16:03
     * @context 设置基础连接路径
     */
    @Override
    public HttpHandler baseUrl(String url) {
        this.mBaseUrl = url;
        return this;
    }

    /**
     * @author linzi
     * @date 2022-11-3 16:54
     * @context 设置拦截器
     */
    @Override
    public HttpHandler setInterceptor(IInterceptor interceptor) {
        mInterceptors.put(interceptor.getClass().getName(), interceptor);
        return this;
    }

    /**
     * @author linzi
     * @date 2022-8-17 16:04
     * @context 设置响应的解析器，不设置默认使用GSON解析
     */
    @Override
    public HttpHandler responseDecoder(IResponseDecoder decoder) {
        this.mResDecoder = decoder;
        return this;
    }

    /**
     * @author linzi
     * @date 2022-7-20 15:52
     * @context 分发请求
     */
    @Override
    public void request(HttpType type, String url, HttpParams params, IBaseCallBack callBack) {
        //解析替换路径参数
        url = formatPathParmas(params, url);
        try {
            if (type == HttpType.GET) {
                get(url, type, params, callBack);
            } else {
                otherAction(type, url, params, callBack);
            }
        } catch (Exception e) {
            Log.e(TAG, "request: ", e);
            error(callBack, e);
        }
    }

    /**
     * @param params 创建参数key
     * @return String
     */
    private String createKeyWithParams(HttpParams params) {
        String fmt = "%s%s";
        return String.format(fmt, params.getContentType(), params.getParams());
    }

    /**
     * @param path     请求路径
     * @param httpType 请求类型
     * @param params   请求参数
     * @param callBack 请求回调
     * @author linzi
     * @date 2022-7-20 15:52
     * @context GET请求操作，可优化为策略模式的操作
     */
    @SuppressLint({"SetWorldReadable", "SetWorldWritable"})
    private void get(String path, HttpType httpType, HttpParams params, IBaseCallBack callBack) {
        if (BuildConfig.DEBUG) {
            Log.i(TAG, getTag() + "start GET request");
        }
        String pathTemp;
        if (!TextUtils.isEmpty(params.getParams())) {
            pathTemp = path + "?" + params.getParams();
        } else {
            pathTemp = path;
        }
        String finalPathTemp = pathTemp;
        mExecutor.execute(() -> {
            String requestUrl;
            if (TextUtils.isEmpty(mBaseUrl)) {
                requestUrl = finalPathTemp;
            } else {
                requestUrl = String.format("%s/%s", mBaseUrl, finalPathTemp);
            }
            if (!TextUtils.isEmpty(params.getSingleUrl())) {
                requestUrl = params.getSingleUrl();
            }
            if (BuildConfig.DEBUG) {
                Log.i(TAG, getTag() + "url:" + requestUrl);
                Log.i(TAG, getTag() + "mTimeout:" + mTimeout);
                Log.i(TAG, getTag() + "mReadTimeout:" + mReadTimeout);
                Log.i(TAG, getTag() + "mUseCache:" + mUseCache);
            }
            //处理拦截器
            linkForInterceptorBefor(params);
            //创建连接
            HttpURLConnection connection = null;
            try {
                connection = createConnection(requestUrl, httpType, params, callBack);
                if (TextUtils.isEmpty(params.getSavePath())) {
                    readResponse(connection, callBack);
                } else {
                    //下载
                    downloadAction(connection, params, callBack);
                }
            } catch (Exception e) {
                Log.e(TAG, getTag() + "err:" + e.getMessage());
                error(callBack, e);
            }
            complete(callBack);
            if (connection != null) {
                connection.disconnect();
            }
            if (BuildConfig.DEBUG) {
                Log.i(TAG, getTag() + "complete");
            }
        });
    }

    /**
     * @return 日志输出TAG
     */
    private String getTag() {
        return String.format("【request-%s】", Thread.currentThread().getName());
    }

    /**
     * @param httpType 请求类型
     * @param path     请求路径
     * @param params   请求参数
     * @param callBack 请求回调
     * @author linzi
     * @date 2022-7-20 15:52
     * @context 非Get请求操作，可优化为策略模式的操作
     */
    @SuppressLint({"SetWorldReadable", "SetWorldWritable"})
    private void otherAction(HttpType httpType, String path, HttpParams params, IBaseCallBack callBack) {
        if (BuildConfig.DEBUG) {
            Log.i(TAG, getTag() + "------------------------------------------------------");
            Log.i(TAG, getTag() + "start request");
            Log.i(TAG, getTag() + "METHOD:" + httpType.getType());
        }
        //构建完整请求路径
        String pathTemp = path;
        if (pathTemp.startsWith("/")) {
            pathTemp = pathTemp.substring(1);
        }
        //请求缓存TAG，暂时保留
        String finalPathTemp = pathTemp;
        //异步请求
        mExecutor.execute(() -> {
            String requestUrl;
            if (TextUtils.isEmpty(mBaseUrl)) {
                requestUrl = finalPathTemp;
            } else {
                requestUrl = String.format("%s/%s", mBaseUrl, finalPathTemp);
            }
            //如果请求使用了自定义请求路径则舍弃之前的请求地址构建的路径
            if (!TextUtils.isEmpty(params.getSingleUrl())) {
                requestUrl = params.getSingleUrl();
            }
            if (BuildConfig.DEBUG) {
                Log.i(TAG, getTag() + "url:" + requestUrl);
                Log.i(TAG, getTag() + "mTimeout:" + mTimeout);
                Log.i(TAG, getTag() + "mReadTimeout:" + mReadTimeout);
                Log.i(TAG, getTag() + "mUseCache:" + mUseCache);
            }
            linkForInterceptorBefor(params);
            Log.i(TAG, getTag() + "run: " + params.getBoundary());
            HttpURLConnection connection = null;
            try {
                connection = createConnection(requestUrl, httpType, params, callBack);
                writeRequestParams(connection, params);
                //读取响应结果
                if (TextUtils.isEmpty(params.getSavePath())) {
                    readResponse(connection, callBack);
                } else {
                    downloadAction(connection, params, callBack);
                }

            } catch (Exception e) {
                e.printStackTrace();
                Log.e(TAG, getTag() + "err:" + e.getMessage());
                error(callBack, e);
            }

            complete(callBack);
            if (connection != null) {
                connection.disconnect();
            }
            if (BuildConfig.DEBUG) {
                Log.i(TAG, getTag() + "complete");
            }
        });
    }

    /**
     * 写入请求参数
     *
     * @param connection 连接对象
     * @param params     请求参数
     * @throws Exception 异常信息
     */
    private void writeRequestParams(HttpURLConnection connection, HttpParams params) throws Exception {
        // 发送请求参数
        OutputStream outputStream = connection.getOutputStream();
        DataOutputStream dos = new DataOutputStream(outputStream);
        switch (params.getContentType()) {
            //表单提交mime
            case FORM_DATA:
                if (BuildConfig.DEBUG) {
                    Log.i(TAG, getTag() + "\n" + params.getFormData());
                }
                dos.writeBytes(params.getFormData());
                Map<String, File> files = params.getFiles();
                StringBuilder sb = new StringBuilder();
                for (String s : files.keySet()) {
                    sb.append(String.format("--%s\r\n", params.getBoundary()));
                    sb.append(String.format("Content-Disposition: form-data; name=\"%s\"; filename=\"%s\"\r\n", s, Objects.requireNonNull(files.get(s)).getName()));
                    sb.append("\r\n");
                    dos.writeBytes(sb.toString());
                    FileInputStream fis = new FileInputStream(files.get(s));
                    int bufferSize = 1024;
                    byte[] buffer = new byte[bufferSize];
                    int length;
                    while ((length = fis.read(buffer)) != -1) {
                        // 将资料写入DataOutputStream中
                        dos.write(buffer, 0, length);
                    }
                    dos.writeBytes("\r\n");
                    fis.close();
                }
                if (files.size() > 0) {
                    dos.writeBytes(String.format("--%s--\r\n", params.getBoundary()));
                }
                break;
            case DEFAULT_FORM:
                if (BuildConfig.DEBUG) {
                    Log.i(TAG, getTag() + "" + params.getParams());
                }
                dos.writeBytes(params.getParams());
                break;
            case APPLICATION_JSON:
                if (BuildConfig.DEBUG) {
                    Log.i(TAG, getTag() + "" + params.getJsonData());
                }
                dos.writeBytes(params.getJsonData());
                break;
            default:
        }
        dos.flush();
        closeSilently(dos);
        closeSilently(outputStream);
    }

    /**
     * 创建非get的连接对象
     *
     * @param requestUrl 请求地址
     * @param httpType   请求类型
     * @param params     参数对象
     * @param callBack   回调对象
     * @return 连接对象
     * @throws Exception 异常信息
     */
    private HttpURLConnection createConnection(String requestUrl, HttpType httpType, HttpParams params, IBaseCallBack callBack) throws Exception {
        URL url = new URL(requestUrl);
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
        connection.setConnectTimeout(mTimeout);
        connection.setReadTimeout(mReadTimeout);
        connection.setDefaultUseCaches(mUseCache);
        connection.setRequestMethod(httpType.getType());
        connection.setDoOutput(true);
        connection.setDoInput(true);
        //设置客户端与服务连接类型
        connection.addRequestProperty("Connection", "Keep-Alive");
        connection.setRequestProperty("Charset", "UTF-8");
        connection.setInstanceFollowRedirects(true);
        if (httpType == HttpType.GET) {
            //设置请求中的媒体类型信息。
            connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
        } else {
            //数据提交类型
            switch (params.getContentType()) {
                case FORM_DATA:
                    connection.setRequestProperty("Content-Type", String.format("%s; boundary=%s", params.getContentType().getType(), params.getBoundary()));
                    break;
                case DEFAULT_FORM:
                    connection.setRequestProperty("Content-Type", String.format("%s; charset=UTF-8", params.getContentType().getType()));
                    break;
                case APPLICATION_JSON:
                    connection.setRequestProperty("Content-Type", String.format("%s", params.getContentType().getType()));
                    connection.setRequestProperty("Content-Length", String.valueOf(params.getJsonData().getBytes(StandardCharsets.UTF_8).length));
                    break;
                default:
            }

            connection.setRequestProperty("Accept", "image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/x-shockwave-flash, application/msword, application/vnd.ms-excel, application/vnd.ms-powerpoint, */*");

        }
        if (BuildConfig.DEBUG) {
            Log.i(TAG, getTag() + "Content-Type:" + params.getContentType().getType());
        }

        //设置请求头
        Map<String, String> headers = params.getHeaders();
        for (String s : headers.keySet()) {
            connection.setRequestProperty(s, headers.get(s));
        }
        start(callBack);
        connection.connect();
        return connection;
    }

    /**
     * 读取响应
     *
     * @param connection 连接
     * @param callBack   回调
     * @throws Exception 异常信息
     */
    private void readResponse(HttpURLConnection connection, IBaseCallBack callBack) throws Exception {
        int httpCode = connection.getResponseCode();
        if (BuildConfig.DEBUG) {
            Log.i(TAG, getTag() + "httpCode:" + httpCode);
        }
        Map<String, List<String>> headerMap = connection.getHeaderFields();
        byte[] result;
        if (httpCode == 200) {
            result = SVUtils.streamTobyte(connection.getInputStream());
        } else {
            result = SVUtils.streamTobyte(connection.getErrorStream());
        }
        if (BuildConfig.DEBUG) {
            Log.i(TAG, getTag() + "callback:" + Arrays.toString(result));
            Log.i(TAG, getTag() + "strCallBack:" + new String(result));
        }
        response(callBack, httpCode, headerMap, result);
    }

    /**
     * 下载操作
     *
     * @param connection 连接对象
     * @param params     参数
     * @param callBack   回调接口
     * @throws Exception
     */
    @SuppressLint({"SetWorldReadable", "SetWorldWritable"})
    private void downloadAction(HttpURLConnection connection, HttpParams params, IBaseCallBack callBack) throws Exception {
        //下载
        InputStream is = connection.getInputStream();
        File temp = new File(params.getSavePath());
        if (temp.exists()) {
            temp.delete();
        }
        boolean res = temp.createNewFile();
        //文件创建失败
        if (!res) {
            error(callBack, new RuntimeException("file permission error"));
            closeSilently(is);
            connection.disconnect();
            return;
        }
        if (!temp.setReadable(true, false) || !temp.setWritable(true, false)) {
            throw new RuntimeException("file permission error");
        }
        try (FileOutputStream os = new FileOutputStream(temp)) {
            byte[] buf = new byte[8 * 1024];
//                            byte[] buf = new byte[1024];
            int len;
            while ((len = is.read(buf)) != -1) {
                os.write(buf, 0, len);
                progress(callBack, len, connection.getContentLength());
            }
            os.flush();
            os.getFD().sync();
        } catch (Exception e) {
            if (temp.exists()) {
                temp.delete();
            }
            error(callBack, e);
        } finally {
            closeSilently(is);
        }
        progress(callBack, connection.getContentLength(), connection.getContentLength());
        response(callBack, connection.getResponseCode(), temp);
    }

    /**
     * 格式化路径参数
     *
     * @param httpParams 请求参数
     * @param url        请求地址
     * @return 请求完整路径
     */
    private String formatPathParmas(HttpParams httpParams, String url) {
        String urlPath = url;
        String pathFormat = "{%s}";
        HashMap<String, Object> pathKey = httpParams.getPathParams();
        for (String s : pathKey.keySet()) {
            String key = String.format(pathFormat, s);
            urlPath = urlPath.replace(key, (String) Objects.requireNonNull(pathKey.get(s)));
        }
        return urlPath;
    }

    /**
     * @param params 模拟链式调用触发拦截器的before
     */
    private void linkForInterceptorBefor(HttpParams params) {
        for (String aClass : mInterceptors.keySet()) {
            Objects.requireNonNull(mInterceptors.get(aClass)).before(params);
        }
    }

    /**
     * @param objs 模拟链式调用触发拦截器 after
     */
    private void linkForInterceptorAfter(Object... objs) {
        for (String aClass : mInterceptors.keySet()) {
            Objects.requireNonNull(mInterceptors.get(aClass)).after(objs);
        }
    }

    /**
     * 回调start
     *
     * @param callBack 回调接口对象
     */
    private void start(IBaseCallBack callBack) {
        if (callBack == null) {
            return;
        }
        mUiHandler.post(callBack::start);
    }

    /**
     * 回调完成
     *
     * @param callBack 回调接口对象
     */
    private void complete(IBaseCallBack callBack) {
        if (callBack == null) {
            return;
        }
        mUiHandler.post(callBack::complete);
        if (BuildConfig.DEBUG) {
            Log.i(TAG, getTag() + "------------------------------------------------------");
        }
    }

    /**
     * 普通回调响应
     *
     * @param callBack 回调接口
     * @param code     网络响应码
     * @param headers  响应请求头
     * @param data     响应的原始数据
     * @param <T>      解码器解码对象
     */
    private <T> void response(IBaseCallBack callBack, int code, Map<String, List<String>> headers, byte[] data) {
        if (callBack == null) {
            return;
        }
        linkForInterceptorAfter(code, headers, data);
        //获取回调中的泛型类
        Type type = ((ParameterizedType) callBack.getClass().getGenericInterfaces()[0]).getActualTypeArguments()[0];
        Class<T> tc = null;
        if (!(type instanceof ParameterizedType)) {
            tc = (Class<T>) type;
        }

        Class<T> finalTc = tc;
        mUiHandler.post(() -> {
            try {
                if (!(type instanceof ParameterizedType)) {
                    //判断是否使用默认解码
                    if (mResDecoder != null) {
                        ((IRequestCallBack) callBack).response(code, headers, mResDecoder.decoder(data, finalTc));
                    } else {
                        ((IRequestCallBack) callBack).response(code, headers, new Gson().fromJson(new InputStreamReader(new ByteArrayInputStream(data)), finalTc));
                    }
                } else {
                    if (mResDecoder != null) {
                        ((IRequestCallBack) callBack).response(code, headers, mResDecoder.decoder(data, type));
                    } else {
                        ((IRequestCallBack) callBack).response(code, headers, new Gson().fromJson(new InputStreamReader(new ByteArrayInputStream(data)), type));
                    }
                }
            } catch (Exception e) {
                ((IRequestCallBack) callBack).error(e);
            }
        });
    }

    /**
     * 文件响应回调
     *
     * @param callBack 回调对象
     * @param code     网络请求码
     * @param data     网络请求原始数据
     */
    private void response(IBaseCallBack callBack, int code, File data) {
        if (callBack == null) {
            return;
        }
        linkForInterceptorAfter(code, data);
        mUiHandler.post(() -> ((IDownLoadCallBack) callBack).response(code, data));
    }

    /**
     * 文件进度回调
     *
     * @param callBack 回调接口对象
     * @param size     总大小
     * @param total    当前大小
     */
    private void progress(IBaseCallBack callBack, long size, long total) {
        if (callBack == null) {
            return;
        }
        mUiHandler.post(() -> ((IDownLoadCallBack) callBack).progress(size, total));
    }

    /**
     * 异常回调
     *
     * @param callBack 回调接口对象
     * @param error    异常信息
     */
    private void error(IBaseCallBack callBack, Throwable error) {
        if (callBack == null) {
            return;
        }
        linkForInterceptorAfter(error);
        mUiHandler.post(() -> callBack.error(error));

    }

    /**
     * 流对象关闭操作
     *
     * @param closeable 流对象
     */
    private void closeSilently(Object closeable) {
        try {
            if (closeable != null) {
                if (closeable instanceof Closeable) {
                    ((Closeable) closeable).close();
                } else {
                    throw new IllegalArgumentException("Unknown object to close");
                }
            }
        } catch (IOException e) {
            Log.e(TAG, "closeSilently: error", e);
        }
    }

    /**
     * https配置
     */
    @SuppressLint("CustomX509TrustManager")
    private static class CusX509TrustManager implements X509TrustManager {

        @SuppressLint("TrustAllX509TrustManager")
        @Override
        public void checkClientTrusted(X509Certificate[] chain, String authType) {
            // TODO Auto-generated method stub

        }

        @SuppressLint("TrustAllX509TrustManager")
        @Override
        public void checkServerTrusted(X509Certificate[] chain, String authType) {
            // TODO Auto-generated method stub

        }

        @Override
        public X509Certificate[] getAcceptedIssuers() {
            // TODO Auto-generated method stub
            return new X509Certificate[0];
        }

    }
}
